// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 这实现了写屏障缓冲区。写屏障本身
// 是gcWriteBarrier，在汇编中实现。
// 
// 有关写屏障的算法详细信息，请参阅mbarier.go。此
// 文件仅处理缓冲区。
// 
// 写屏障有快路径和慢路径。快速路径
// 只需排队到per-P写屏障缓冲区。它是用
// 程序集编写的，不会破坏任何通用寄存器，因此它没有Go调用的一般开销。
// 
// 当缓冲区填满时，写屏障调用慢路径
// （wbBufFlush）将缓冲区刷新到GC工作队列。在这个
// 路径中，由于编译器没有溢出寄存器，我们溢出*all*
// 寄存器并禁止任何可以观察
// 堆栈帧的GC安全点（因为我们不知道溢出的
// 寄存器的类型）。

package runtime

import (
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// testSmallBuf强制一个小的写屏障缓冲区对写进行强调
// 屏障刷新。
const testSmallBuf = false

// wbBuf是由写入屏障排队的指针组成的每P缓冲区。
// 此缓冲区在填满时会刷新到GC工作区，并在
// 各种GC转换上刷新。
// 
// 这与“顺序存储缓冲区”（SSB）密切相关，
// 除了SSB通常用于维护记忆集之外，
// 而它用于标记。
type wbBuf struct {
	// next指向buf中的下一个插槽。它不能是
	// 指针类型，因为它可以指向buf的末尾，并且
	// 必须在无写障碍的情况下更新。
	// 
	// 这是一个指针而不是索引，用于优化
	// 写屏障程序集。
	next uintptr

	// 终点刚过buf终点。它不能是
	// 指针类型，因为它指向buf的末尾，并且必须在没有写障碍的情况下更新
	// 。
	end uintptr

	// buf存储一系列指针以执行写屏障
	// on。这必须是wbBufEntryPointers的倍数，因为
	// 写屏障只对每个条目检查一次溢出。
	buf [wbBufEntryPointers * wbBufEntries]uintptr
}

const (
	// wbBufEntries是写入屏障缓冲区的
	// 刷新之间的写入屏障数。
	// 
	// 这将延迟用于吞吐量摊销。较高的
	// 值会更多地摊销刷新开销，但会增加
	// 刷新延迟。更高的值也会增加缓存
	// 缓冲区的占用空间。
	// 
	// TODO:这项操作的延迟成本是多少？调整此值。
	wbBufEntries = 256

	// wbBufEntryPointers是每个写入屏障添加到
	// 缓冲区的指针数。
	wbBufEntryPointers = 2
)

// reset通过重置b的next和end指针来清空b。
func (b *wbBuf) reset() {
	start := uintptr(unsafe.Pointer(&b.buf[0]))
	b.next = start
	if writeBarrier.cgo {
		// 通过在每个屏障上强制刷新
		// 有效禁用缓冲区。
		b.end = uintptr(unsafe.Pointer(&b.buf[wbBufEntryPointers]))
	} else if testSmallBuf {
		// 对于测试，允许缓冲器中有两个屏障。如果
		// 我们只做了一个，那么非堆指针的屏障
		// 将不会有操作。这使我们可以在以后将缓冲
		// 屏障与刷新结合起来。
		b.end = uintptr(unsafe.Pointer(&b.buf[2*wbBufEntryPointers]))
	} else {
		b.end = start + uintptr(len(b.buf))*unsafe.Sizeof(b.buf[0])
	}

	if (b.end-b.next)%(wbBufEntryPointers*unsafe.Sizeof(b.buf[0])) != 0 {
		throw("bad write barrier buffer bounds")
	}
}

// 放弃重置b的下一个指针，但不重置其结束指针。
// 
// 这必须是nosplit，因为它是由wbBufFlush调用的。
// 
// go:nosplit 
func (b *wbBuf) discard() {
	b.next = uintptr(unsafe.Pointer(&b.buf[0]))
}

// 空报告b是否不包含指针。
func (b *wbBuf) empty() bool {
	return b.next == uintptr(unsafe.Pointer(&b.buf[0]))
}

// putFast将新旧内容添加到写屏障缓冲区，如果需要刷新，则返回
// false。调用方应将其用作：
// 
// buf:=&getg（）.m.p.ptr（）.wbBuf 
// if！布法斯特（旧的，新的）{
// wbBufFlush（…）
// }
// 。。。实际内存写入。。。
// 
// wbBufFlush的参数取决于调用方是否正在执行其自己的cgo指针检查。如果是，则可以是
// wbBufFlush（nil，0）。否则，它必须传递插槽地址和
// new。
// 
// 调用方必须确保在上述序列中没有抢占点。buf处于
// 使用状态时，不得有抢占点，因为它是per-P资源。不能有先发制人的
// 缓冲区放置和写入内存之间的点，因为此
// 可能允许GC相变，这可能导致未写入
// 屏障。
// 
// 必须立即将putFast设置为WriteBarrierRec，因为此处的写屏障会破坏写屏障缓冲区。它（以及它调用的所有东西，如果它调用了任何东西）必须是nosplit，以避免调度到不同的P和不同的缓冲区。
// wbBufFlush将当前P的写屏障缓冲区刷新到GC 
func (b *wbBuf) putFast(old, new uintptr) bool {
	p := (*[2]uintptr)(unsafe.Pointer(b.next))
	p[0] = old
	p[1] = new
	b.next += 2 * sys.PtrSize
	return b.next != b.end
}

// workbufs。
// 导致刷新的写屏障的插槽和值被传递，以便它可以实现cgocheck。
// 
// 这不能有写屏障，因为它是写
// 屏障实现的一部分。
// 
// 这和它调用的所有内容都必须是nosplit，因为1）堆栈
// 包含来自gcWriteBarrier的非类型化插槽，2）在调用方的写屏障测试和
// 刷新缓冲区之间不能有GC安全点。
// 
// TODO:一个“go:nosplitre”注释将非常适合于此。
// 
// go:nowritebarrierrec 
// go:nosplit 
func wbBufFlush(dst *uintptr, src uintptr) {
	// 注意：此函数的所有可能返回必须重置
	// 缓冲区的下一个指针以防止缓冲区溢出。

	// 此*不能*修改其参数，因为此
	// 函数的参数插槽在gcWriteBarrier 
	// 中作为寄存器溢出插槽执行双重任务。目前，不修改
	// 参数就足以使溢出槽保持未修改的
	// （这似乎不太可能改变，因为它花费很少，而且
	// 有助于调试）。

	if getg().m.dying > 0 {
		// 我们要下去了。写屏障
		// 没有多大意义，这样我们就可以在
		// 紧急路径中允许写屏障。
		getg().m.p.ptr().wbBuf.discard()
		return
	}

	if writeBarrier.cgo && dst != nil {
		// 必须从执行
		// 写入操作的堆栈中调用此函数。一路上都没有裂缝。
		cgoCheckWriteBarrier(dst, src)
		if !writeBarrier.needed {
			// 我们只被要求参加cgocheck。
			getg().m.p.ptr().wbBuf.discard()
			return
		}
	}

	// 切换到系统堆栈，这样我们就不必担心
	// 非类型的堆栈插槽或安全点。
	systemstack(func() {
		wbBufFlush1(getg().m.p.ptr())
	})
}

// wbBufFlush1将p的写屏障缓冲区刷新到GC工作队列。
// 
// 这不能有写屏障，因为它是写
// 屏障实现的一部分，因此这可能会导致无限循环或
// 缓冲区损坏。
// 
// 这必须是不可抢占的，因为它使用P的workbuf。
// 
// go:nowritebarrierrec 
// go:systemstack 
func wbBufFlush1(_p_ *p) {
	// 获取缓冲指针。
	start := uintptr(unsafe.Pointer(&_p_.wbBuf.buf[0]))
	n := (_p_.wbBuf.next - start) / unsafe.Sizeof(_p_.wbBuf.buf[0])
	ptrs := _p_.wbBuf.buf[:n]

	// 在处理缓冲区时，对缓冲区下毒，以确保没有任何东西排队。
	_p_.wbBuf.next = 0

	if useCheckmark {
		// 选中标记模式的慢路径。
		for _, ptr := range ptrs {
			shade(ptr)
		}
		_p_.wbBuf.reset()
		return
	}

	// 标记缓冲区中的所有指针，只记录我们变灰的指针。我们使用缓冲区本身临时
	// 记录灰色指针。
	// 
	// TODO:scanobject/scanblock是否应该将指针填充到wbBuf中？然后，这将成为唯一的灰色道路。
	// 
	// TODO:如果堆栈已着色，我们可以避免对缓冲区中的任何“新”指针着色，甚至可以避免将它们放入缓冲区（这将使其
	// 容量加倍）。这对于缓冲区来说有点复杂；我们可以跟踪任何未着色的goroutine是否使用了
	// 缓冲区，或者只是全局跟踪是否存在任何
	// 未着色的堆栈，并在每次堆栈扫描后刷新。
	gcw := &_p_.gcw
	pos := 0
	for _, ptr := range ptrs {
		if ptr < minLegalPointer {
			// 零指针非常常见，尤其是
			// 用于“旧”值。尽快过滤掉这些和
			// 其他“明显的”非堆指针。
			// 
			// TODO:我们是否应该在快速
			// 路径中过滤掉零以降低刷新率？ABCFDG 
			continue
		}
		obj, span, objIndex := findObject(ptr, 0, 0)
		if obj == 0 {
			continue
		}
		mbits := span.markBitsForIndex(objIndex)
		if mbits.isMarked() {
			continue
		}
		mbits.setMarked()

		// 标记span。
		arena, pageIdx, pageMask := pageIndexOf(span.base())
		if arena.pageMarks[pageIdx]&pageMask == 0 {
			atomic.Or8(&arena.pageMarks[pageIdx], pageMask)
		}

		if span.spanclass.noscan() {
			gcw.bytesMarked += uint64(span.elemsize)
			continue
		}
		ptrs[pos] = obj
		pos++
	}

	// 将灰色对象排队。
	gcw.putBatch(ptrs[:pos])

	_p_.wbBuf.reset()
}
